Add AArch64 Windows target support to exploit/windows/smb/psexec - #21779
Add AArch64 Windows target support to exploit/windows/smb/psexec#21779bwatters-r7 wants to merge 4 commits into
Conversation
The AArch64 Windows PE loader template (to_winaarch64pe) and the windows/aarch64/exec payload already existed, but nothing actually wired ARCH_AARCH64 into the executable-generation paths psexec (and msfvenom/RPC) depend on: - lib/msf/util/exe.rb: to_executable_fmt had no ARCH_AARCH64 case for exe/exe-service/exe-small/exe-only/msi/msi-nouac, so requesting any of those formats for AArch64 silently produced no output. - lib/msf/core/exploit/exe.rb: generate_payload_exe_service (what psexec's native_upload calls) hard-coded an X64-or-X86 choice, so an AArch64 payload would have been embedded in a broken x86 PE. - lib/msf/util/exe/windows/aarch64.rb: to_winaarch64pe had no bounds check on the template's fixed 8192-byte payload buffer, unlike the analogous to_win32pe_old. Also add ARCH_AARCH64 to psexec's Automatic/Native upload/MOF upload targets, and make Automatic skip the PowerShell-delivery branch for AArch64 payloads (the rex-powershell injection wrapper only knows how to spawn x86/x64 powershell.exe). Only one AArch64 Windows payload exists upstream so far (windows/aarch64/exec, single-stage command exec, no stager/meterpreter for ARM64 Windows yet), so this enables command execution via psexec against Windows-on-ARM targets, not a full session. Verified live against a real Windows AArch64 host: Native upload with PAYLOAD windows/aarch64/exec correctly generates and drops an ARM64 PE, registers/starts/removes it as a service, and the embedded WinExec shellcode executes as NT AUTHORITY\SYSTEM. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Hey, @bwatters-r7. Just say which workflow you prefer. 👍 |
There was a problem hiding this comment.
Pull request overview
Adds Windows ARM64 (AArch64) support to the exploit/windows/smb/psexec module by allowing AArch64 payload selection and routing Automatic targeting away from the PowerShell delivery path, while extending the framework’s Windows EXE generation paths to emit AArch64 executables (including when an exe-service is requested).
Changes:
- Extend
psexecmodule targets (Automatic/Native/MOF) to includeARCH_AARCH64, and force Automatic to select native upload when an AArch64 payload is chosen. - Add an explicit maximum payload size guard for the Windows AArch64 PE template generator.
- Thread
ARCH_AARCH64through EXE format selection forexe-serviceand related Windows EXE outputs, and add AArch64 handling ingenerate_payload_exe_service.
Impact Analysis:
- Blast radius: medium — affects
modules/exploits/windows/smb/psexecusers plus any consumers of Windows EXE generation (Msf::Util::EXE/Msf::Exploit::EXE), including payload generation flows. - Data and contract effects: introduces a hard size limit for AArch64 template-based EXE generation (errors on oversize payloads) and changes behavior when requesting Windows exe-service output for AArch64 (mapped to the loader template).
- Rollback and test focus: rollback is straightforward (revert commits); test focus on AArch64 payload EXE generation (success + oversize failure) and
psexecnative upload execution path with an AArch64 payload.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| modules/exploits/windows/smb/psexec.rb | Allows AArch64 payloads and makes Automatic target choose native upload for AArch64. |
| lib/msf/util/exe/windows/aarch64.rb | Adds a fixed-size template limit constant and enforces a maximum embedded payload size. |
| lib/msf/util/exe.rb | Adds AArch64 support for multiple Windows EXE format branches, including exe-service. |
| lib/msf/core/exploit/exe.rb | Updates generate_payload_exe_service to generate an AArch64 Windows PE when requested. |
Suppressed comments (1)
lib/msf/util/exe/windows/aarch64.rb:46
- Important: Problem:
to_winaarch64peusescode.lengthand raises a genericRuntimeErroron overflow, which is fragile for binary encodings and makes targeted rescue harder in library code. Impact: payload size checks can be inconsistent and callers can’t reliably rescue a specific error type. Fix: usecode.bytesizeand raise a more specific error (e.g.,ArgumentError) with the actual size.
if code.length > WINAARCH64_PAYLOAD_SPACE
raise RuntimeError, "The Windows AArch64 EXE generator has a max size of " \
"#{WINAARCH64_PAYLOAD_SPACE} bytes, please fix the calling module"
end
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| [ 'Native upload', { # upload a service executable | ||
| 'Arch' => [ARCH_X86, ARCH_X64], | ||
| 'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64], | ||
| 'Payload' => { 'Space' => 2 ** 30 } # service executables place the payload within a segment, 1GiB is a practical max in many cases | ||
| } ], | ||
| [ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64] } ], | ||
| [ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64] } ], |
| def to_winaarch64pe(framework, code, opts = {}) | ||
| # Use the standard template if not specified by the user. | ||
| # This helper finds the full path and stores it in opts[:template]. | ||
| set_template_default(opts, 'template_aarch64_windows.exe') | ||
|
|
||
| # Read the template directly from the path now stored in the options. | ||
| pe = File.read(opts[:template], mode: 'rb') | ||
|
|
||
| # Find the tag and inject the payload | ||
| bo = find_payload_tag(pe, 'Invalid Windows AArch64 template: missing "PAYLOAD:" tag') | ||
|
|
||
| if code.length > WINAARCH64_PAYLOAD_SPACE | ||
| raise RuntimeError, "The Windows AArch64 EXE generator has a max size of " \ | ||
| "#{WINAARCH64_PAYLOAD_SPACE} bytes, please fix the calling module" | ||
| end |
There was a problem hiding this comment.
This is fine for now, but probably will need to be addressed once meterpreter for arm64 will exists
|
@vinicius-batistella I don't think I can add you as a collaborator, since that permission appears to be at the repo level if I'm reading correctly. Feel free to PR to this branch, though! I'll be out of touch for a couple days but I should pop back up next week. |
|
Great, no worries. I'll do that way. Thanks. |
The AArch64 Windows PE generator was added in this branch without a
matching spec, unlike its x86/x64 counterparts in
spec/lib/msf/util/exe/windows/common_spec.rb. Cover the two behaviours
that matter for callers that now route AArch64 payloads through it
(psexec, msfvenom, RPC):
- a small payload is written at the "PAYLOAD:" tag offset in the
template, the returned bytes are still an MZ/PE the same size as
the template, and the surrounding template bytes are untouched;
- a payload larger than WINAARCH64_PAYLOAD_SPACE raises the
documented RuntimeError instead of silently overflowing the
fixed-size buffer.
The oversize test computes its input from
Msf::Util::EXE::Windows::Aarch64::ClassMethods::WINAARCH64_PAYLOAD_SPACE
so widening the template buffer will surface the change here.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add RSpec coverage for Msf::Util::EXE.to_winaarch64pe
|
Looks like the MOF Upload option successfully uploads the MOF File, but then does not get execution. That behavior is matched on x64 Windows 11, so it is not a regression in AARCH64, but no longer a feature on Windows. |
native_upload_with_workaround now rescues the RuntimeError raised when a payload exceeds an architecture's generator size (e.g. an AArch64 payload larger than 8192 bytes via the 'Native upload' target, which advertises a 1GiB Payload Space) and turns it into a normal fail_with instead of an unhandled exception. Also attempted to pad to_winaarch64pe's injected shellcode out to the full 8192-byte template buffer (matching to_win32pe_old's pattern), but reverted that: it broke a verified-working 'Native upload' run (ERROR_BAD_EXE_FORMAT/193) against a live AArch64 Windows target, so the template's reserved payload region is evidently not safely fillable to its full nominal size. Left as-is pending further investigation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
I noticed that we don't support powershell execution because we don't have support for powershell injection on AARCH64 in the rex-powershell library. I think we'd need to add it here: https://github.com/rapid7/rex-powershell/blob/master/lib/rex/powershell/payload.rb |
Correct. The WBEM + MOF auto exec technique was Windows XP / 2003 era only. Dropped in Vista / 7. |
|
Hey, @bwatters-r7. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (4)
lib/msf/util/exe/windows/aarch64.rb:46
- Important: Problem:
to_winaarch64peraises a genericRuntimeErrorfor oversize payloads. Impact: callers have to rescue overly-broad exceptions (or crash) which can mask unrelated runtime failures. Fix: raise a more specific exception (e.g.ArgumentError) and include the actual payload size in the message.
if code.length > WINAARCH64_PAYLOAD_SPACE
raise RuntimeError, "The Windows AArch64 EXE generator has a max size of " \
"#{WINAARCH64_PAYLOAD_SPACE} bytes, please fix the calling module"
end
modules/exploits/windows/smb/psexec.rb:129
- Important: Problem:
native_upload_with_workaroundrescuesRuntimeError, which can swallow unrelated runtime failures insidenative_upload. Impact: real errors can be misreported as payload-generation failures, making debugging harder. Fix: rescue the specific exception type raised for oversize AArch64 payloads (e.g.ArgumentErrorafter tightening the generator) instead of allRuntimeErrors.
rescue RuntimeError => e
# generate_payload_exe_service can raise a plain RuntimeError when the
# encoded payload doesn't fit the target architecture's generator (e.g.
# AArch64's loader template is capped at 8192 bytes, well under the
# 1GiB this target advertises for 'Native upload'). Surface that as a
# normal exploit failure instead of an unhandled exception.
fail_with(Msf::Exploit::Failure::PayloadFailed, "#{peer} - Failed to generate the service executable: #{e.message}")
spec/lib/msf/util/exe/windows/aarch64_spec.rb:49
- Important: Problem: the spec expects
RuntimeError, but the generator should raise a more specific exception for invalid input sizes (e.g.ArgumentError). Impact: tightening the generator’s error type will break this spec. Fix: update the expectation to match the new exception type.
it 'raises when the payload exceeds the template payload buffer' do
oversized = 'B'.b * (max_payload_space + 1)
expect { Msf::Util::EXE.to_winaarch64pe(nil, oversized, template: template) }
.to raise_error(RuntimeError, /max size of #{max_payload_space} bytes/)
end
modules/exploits/windows/smb/psexec.rb:79
- Suggestion: Problem: the inline reference to
Msf::Util::EXE::Windows::Aarch64::WINAARCH64_PAYLOAD_SPACEis incorrect because the constant is defined underClassMethods. Impact: future maintainers will be sent to a non-existent constant path. Fix: update the comment to point at the real constant name/location.
# service executables place the payload within a segment, 1GiB is a practical max in many cases.
# AArch64 is a notable exception: it has no dedicated service template yet, so it reuses the
# loader template's fixed 8192-byte buffer (see Msf::Util::EXE::Windows::Aarch64::WINAARCH64_PAYLOAD_SPACE).
# native_upload_with_workaround rescues the resulting RuntimeError if an AArch64 payload is too big.
|
Hey @bwatters-r7 — took a swing at the rex-powershell side and opened rapid7/rex-powershell#49. |
dledda-r7
left a comment
There was a problem hiding this comment.
Code looks good to me, i will double check on a live system and land it.
Description
We have AARCH64 shell payloads! This adds support for them into the psexec module.
Breaking Changes
None
Reviewer Notes
Verification Steps
Test Evidence
Environment